//////////////////////////////////////////////////////////////////////////////////
// hunter.c
// Implementation of decideMove(), AI to play 'Dracula's Fury' - Van Herpderp
// Author(s): Alexander Saunders, Pravin Rhadakrishnan, Ashleey Asokkumar
// TeamName: TheYellowFormPolicy
// Tutorial Group: Thursday
// Tutor: Sim
// Date: 19/01 22:01
// Version: 2
//////////////////////////////////////////////////////////////////////////////////

//The aim is to find the youngest, highest priority item. Though the number of turns can grow large, the number of relevant encounters stays
//small. An earlier encounter of the same priority will always be more relevant than a later one.
//Priority + dMoveAge = max distance. I.e. a confrontation will have priority 0 + age, a trap will have at worst priority 6 + draculaMoves

//All is not lost, you can still use the Dracual half of your function to find encounters, however you can't read the player past strings
//you can use the past moves and analyse player health and state of rest to play more intelligently. Keep the hope alive. You only need to
//re-write half your function :P


#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

#include "game.h"
#include "HunterView.h" 
//defines used: GODALMING 0, SEWARD 1, HELSING 2, MINA 3 #include HunterView.c (TRUE 1, FALSE 0), 
#include "hunter.h"

#include "findDracula.c" 


//File specific prefix for #defines is 'HU_'
#define HU_PLAYER_0_START 34 //Madrid
#define HU_PLAYER_1_START 9  //Bucharest
#define HU_PLAYER_2_START 36 //Marseilles
#define HU_PLAYER_3_START 6  //Ich bin ein Berlinner

#define HU_NO_BEST_MOVE -1
#define HU_NO_BEST_GUESS -1
#define HU_MAX_USEFUL_ENCOUNTERS 7

#define HU_MAX_PREDICTS 2000

//Priority + dTurns = accuracy or "chances to move". Priority can be negative.
typedef struct _draculaSighting{
    int loc;
    int dTurns;
    int priority;
}encounter;


//Dumb functions
void legalRoadMoves(int playerLoc[], HunterView hv); //Returns an array containing all legal road moves, [0] is #moves
int findPlayerLoc(HunterView hv);                    //Returns the player's location
int decideMoveRand(int legalMovesNum);               //Randomly picks a legal move

//Advanced functions
int decideMoveFollow(HunterView gameState, int legalRoadMoves[]); //Returns a legal move that most likely contains Dracula if one exists 
encounter draculaLastSeen(HunterView gameState);                                        //Returns dracula's last known location
int draculaSigns(HunterView gameState, encounter recentEncounters[], encounter latest); //Returns an array containing all encounters

//------------------------------------------------------------------------------------------------------------------------------------


//This is the main function called by the game
void decideMove(HunterView gameState){
    
    //Record a random move
    int moveList[SEA_END + 1];
    int i;
    for (i = 0; i <= SEA_END + 1; i++){
        moveList[i] = 0;
    }
    //Find possible road moves
    legalRoadMoves(moveList, gameState);
    
    //"Decide" on a move
    char moveToCity[3];
    
    int moveListIndex = decideMoveRand(moveList[0]);

    int moveID = moveList[moveListIndex];
    getMoveCode(moveToCity, moveID);
    
    
    playerMessage message = {"Still a better story than Twilight."};
    registerBestPlay(moveToCity , message);
    
    
    //Record a directed move 
    moveID = decideMoveFollow(gameState, moveList);
    if(moveID > HU_NO_BEST_MOVE){
        getMoveCode(moveToCity, moveID);
        
        playerMessage message = {"I could've been smart. I'm still smarter than Stephanie Meyers"};
        registerBestPlay(moveToCity , message);
        
    }
    
}

//------------------------------------------------------------------------------------------------------------------------------------
//Helper Functions

//Chooses a move that will take the hunter closer to Dracula's predicted location
//if no good information is available then it makes a random move
int decideMoveFollow(HunterView gameState, int legalRoadMoves[]){
    
    int finalMove = HU_NO_BEST_MOVE;
       
    //Try to approximate Dracula's location
    encounter recentEncounters[HU_MAX_USEFUL_ENCOUNTERS];
    
    encounter latest; // Stub
    latest.dTurns = -1; //This is a stub value
    
    //Attempt to find the most accurate guess of Dracula's location
    int best = draculaSigns(gameState, recentEncounters, latest);
   
    if (best != HU_NO_BEST_MOVE){
        //If we have a useful encounter, try to predict where Dracula is now
        int predictedDracLocs[HU_MAX_PREDICTS] = {HU_NO_BEST_GUESS};
        int bestMove = draculaMostLikely(gameState, recentEncounters[best].loc, recentEncounters[best].dTurns, predictedDracLocs);
        
        bestMove = compareLists(predictedDracLocs, legalRoadMoves);
        
        finalMove = bestMove;
    }
    
    return finalMove;
}


//Fills the list recentEncounters and returns the highest priority value
int draculaSigns(HunterView gameState, encounter recentEncounters[], encounter latest){

    
    //Initialisation
    latest.dTurns = HU_NO_BEST_GUESS;//STUB VALUE
    
    int numMoves = turnNumber(gameState);
    int numEncounters = 0;
    
    //Get the list of dracula moves
    int numDracMoves = numDraculaMoves(gameState);
    int dMoves[numDracMoves];
    draculaMoves(gameState, dMoves);

    //Set modifiers which alter the priority of encounters based on hides/double backs
    int hideModifier = 0;
    int doubleBackModifier = 0;
    int bestAccuracy = numMoves;
    int mostAccurate = HU_NO_BEST_GUESS;
    
    //Counter goes back from most recent move
    int i = numDracMoves - 1;

    
    while((i >= 0) && (numEncounters < HU_MAX_USEFUL_ENCOUNTERS) && (bestAccuracy > 0)){

          
        if((dMoves[i] > DRACULA_HIDE) && (dMoves[i] < DRACULA_TELE)){ //A Double Back
        
            //In the event of a double back, encounters made after it do not have their priority effected
            //Priority can be decreased, or not. As such I picked a middle value in the absence of more
            //complex analysis
            
            doubleBackModifier = (dMoves[i] - DRACULA_HIDE);
            if(doubleBackModifier > 3){
                doubleBackModifier = doubleBackModifier / 2;
            }
            doubleBackModifier = doubleBackModifier * -1;
            
        }else if(dMoves[i] == DRACULA_HIDE){ //A Hide
            //Decrement hide modifier, i.e. offset potential moves by 1
            hideModifier--;
            
        }else if((dMoves[i] < DRACULA_CITY) || (dMoves[i] == DRACULA_TELE)){ //A known city
            //Store encounter
            if (dMoves[i] != DRACULA_TELE){
                recentEncounters[numEncounters].loc = dMoves[i];
            }else{
                recentEncounters[numEncounters].loc = 13; //Dracula's castle
            }
            recentEncounters[numEncounters].dTurns = numDracMoves -1 - i;
            recentEncounters[numEncounters].priority = 0 + hideModifier + doubleBackModifier;
            
            //Check if a new most accurate encounter has been found
            if(recentEncounters[numEncounters].priority + recentEncounters[numEncounters].dTurns < bestAccuracy){
                bestAccuracy = recentEncounters[numEncounters].priority + recentEncounters[numEncounters].dTurns;
                mostAccurate = numEncounters;
            }
            
            //Increment number of encounters
            numEncounters++;
        }
    
        //Decrement number of turns
        i--;
    }
    
    //Assert the encounters are useful
    if(bestAccuracy > 3){
        mostAccurate = HU_NO_BEST_MOVE;
    }
    
    return mostAccurate;
}

        
        
//Randomly pick a legal move
int decideMoveRand(int legalMovesNum){
    srand(time(NULL));
    int moveNum;
    
    if(legalMovesNum > 1){
        //Pick a number between 1 and total number of legal moves
        moveNum = (rand() % legalMovesNum) + 1;
    }else{
        //Special case
        moveNum = 1;
    }
    return moveNum;
}


//Given the gamestate returns a list of all possible road moves
void legalRoadMoves(int moveList[], HunterView hv){
    
    if(turnNumber(hv) > 3){
        int playerLoc = findPlayerLoc(hv);
        
        //Declare adjacency matrix
        Connection **graph = malloc(sizeof(Connection*)*(SEA_END+1));
        int i, j;
        for (i = 0; i <= SEA_END; i++){
            graph[i] = malloc(sizeof(Connection)*(SEA_END+1));
            for (j = 0; j <= SEA_END; j++){
                graph[i][j] = malloc(sizeof(struct connection));
            }
        }
        
        giveGraph (hv, graph);
        
        
        //Copy legal moves to moveList[]
        j = 1;
        for (i = 0; i <= SEA_END; i++){
            if(graph[playerLoc][i]->road == 1){
                moveList[j] = i;
                j++;
            }
        }
        
        //Element 0 contains the number of possible moves
        moveList[0] = j - 1;
    }else{
        //Special case for the first move
        int playerNum = whoAmI(hv);
        moveList[0] = 1;
        if(playerNum == 0){
            moveList[1] = HU_PLAYER_0_START;
        }else if(playerNum == 1){
            moveList[1] = HU_PLAYER_1_START;
        }else if(playerNum == 2){
            moveList[1] = HU_PLAYER_2_START;
        }else{
            moveList[1] = HU_PLAYER_3_START;
        }
    }
}


//Returns the player location by querying an array of the past 4 moves.
int findPlayerLoc(HunterView hv){
    
    Move lastMove[NUM_HUNTERS];
    int i;
    for (i = 0; i < NUM_HUNTERS; i++){
        lastMove[i] = malloc(sizeof (struct move));
    }
    //returns the last 4 player moves including the current player's
    //previous move. moves[0] is player 0, moves[3] is player 3 etc.
    movesSinceTurn(hv, lastMove);

    return lastMove[whoAmI(hv)]->location;
}


